/**
 * 与网络请求有关的全部东西
 * Created by haua on 16/8/12.
 */


(function () {

    if(!window.H)
        window.H = {};

    //刷新本页
    H.refesh = function(){
        location.href = this.urlReplaceArg(location.href,'refeshRand',Math.random());
    };

    /**
     * 侦听浏览器返回按钮的点击事件。
     * ps1.每调用一次本函数,本浏览器窗口会重定向到一个新页面,但是页面不会刷新,所以在用户看来除了地址变了,其它什么都没变,
     * 而在浏览器看来是跳到了新页面,只是没有加载新页面的元素
     * @param fn function 当用户点击浏览器返回按钮,返回到调用本函数时所在页面时执行。
     *                      ps.因为允许多次调用本函数,所以有可能会跳了很多个页面,所以不是每次点返回键都会调用这个函数
     * @param [one=true] bool 这个事件是否只响应一次,true是,false否。默认true。
     * */
    H.onBackBtnClick = function(fn,one) {
        var mark = Math.random(),
            thisUrl = location.href;//保存当前链接

        //添加一个新页面到浏览器历史(只有做这一步,点击返回按钮才不会返回上一页,而是留在本页并触发事件)
        history.pushState({}, "", "#newWin&mark="+mark);

        window.addEventListener('popstate',back);

        function back(){
            if(location.href==thisUrl){//现在的链接和之前保存的一致的话,就执行
                console.log(location.href,thisUrl);
                fn&&fn();
                if(one!==false)
                    window.removeEventListener('popstate',back);//让这个事件只响应一次
            }
        }
    };

    /**
     * 替换url中的queryString的值
     * @param url
     * @param k object|string 这个可以是键值对的对象，也可以只是key名
     * @param v 如果值为void(0)，则连key一起去掉
     * */
    H.urlReplaceArg = function(url,k,v){
        if(typeof url!=='string')return url;
        if(typeof k!=='object'){
            var kk = {};
            kk[k] = v;
            k = kk;
        }
        var urls = url.split('#');
        url = urls[0];
        for(var i in k){
            if(k.hasOwnProperty(i)){
                var reg = new RegExp('([&?]'+i+'\\=)[^?&]*','g');//两个右斜杠，在变为reg后会变成一个的
                if(k[i]===void(0)){
                    url = url.replace(reg,'');
                }else{
                    k[i] = encodeURIComponent(decodeURIComponent(k[i]));//先decode再encode，是为了兼容旧版本，过一年后旧版本应该都不用了，就可以直接encode
                    if(url.search(reg)>=0){
                        // 这是只匹配(?&)和其后面的值，匹配出来的$1是?&之一。
                        // 原字符串未被正则匹配的部分是不会变的，replace之后只改匹配成功的部分，而不是只生成后面表达式的部分
                        url = url.replace(reg,'$1'+k[i]);
                    }else{
                        url = url+'&'+i+'='+k[i];
                    }
                }
            }
        }

        if(url.indexOf('?')<0&&url.indexOf('&')>=0){//有&而没有?，把第一个&改成?
            url = url.replace('&','?')
        }
        return url+(urls[1]||'');
    };

    H.queryString = function(url){
        url = (url||location.href).split('#');
        var obj={'_hash':url[1]||''},
            qs = url[0].split('?');
        obj['_origin'] = qs[0];
        if(!qs[1])
            return obj;
        qs = qs[1].split('&');
        for(var i=0,sNum=qs.length;i<sNum;i++){
            var a = qs[i].split('=');
            obj[a[0]] = decodeURIComponent(a[1]);
        }
        return obj
    };
    
    /**
     * 预加载图片
     * */
    H.preloadImgs = function(imgs,fn){
        for(var i=0,iNum=imgs.length,hasLoaded=0;i<iNum;i++){
            loadImg(imgs[i],function () {
                hasLoaded++;
                if(hasLoaded==iNum){
                    fn&&fn();
                }
            })
        }
        function loadImg(src,fn){
            var img = document.createElement('img');
            img.onload = fn;
            img.src = src;
        }
    };

    /**
     * 这是一个对象方法，需要实例化
     * 用于一个页面多次请求同一个接口数据的情况，比如分页。
     * 一个基于jq，且封装好一些功能的用于发起Ajax请求的对象
     * @param url string
     * @param [args] mixed
     * @param fn function
     * @param [option]=null obj
     * @param option.firstPage = 1 int 第一页是几
     * @param option.pageName = page string 接口中“页数”的参数名，默认page
     * @param option.pageNum = null int 每页数据的数量，默认不设置，后台多少数据就多少
     * @param option.pageNumName = pagenum string 接口中“每页数据的数量”的参数名，默认pagenum
     * @param option.autoStart = true boolean 实例化对象之后自动加载第一页数据。
     * @param option.waitForFinish = true boolean 是否必须等待上一次请求完成（不论成功还是失败）才能再用这个对象获取数据。
     * */
    H.Ajax = function(url,args,fn,option){
        if(typeof args=='function'){
            option = fn;
            fn = args;
            args = null;
        }

        this.baseurl = url;
        this.args = args||{};
        this.finishFn = fn;
        this.option = option||{};
        this.pageName = this.option.pageName||'page';
        this.pageNum = this.option.pageNum;
        this.pageNumName = this.option.pageNumName||'pagenum';
        this.waitForFinish = this.option.waitForFinish!==false;
        this.nowPage = typeof this.option.firstPage=='number'?this.option.firstPage-1 : 0;//因为每次获取数据都会先给这个+1，如果传进来参数不-1，则第一次获取的数据就不对了
        this.type = 0;//0可以进行下一次获取数据，1获取数据中，不能进行下一次获取数据。

        if(this.option.autoStart!==false)
            this.next();
    };
    H.Ajax.prototype = {
        /**
         * 获取指定页面的数据
         * @param page int
         * @return boolean 有可能失败的，失败的时候返回false
         * */
        getData:function(page){
            var thi = this;
            if(thi.waitForFinish&&thi.type!=0)
                return false;
            thi.type = 1;
            page = page || thi.nowPage || 1;

            var pageObj = {};
            pageObj[thi.pageName] = page;
            if(thi.pageNum)pageObj[thi.pageNumName] = thi.pageNum;

            var args = typeof thi.args=='object'?
                $.extend(pageObj,thi.args):
                (thi.pageName+'='+page+(thi.pageNum?'&'+thi.pageNumName+'='+thi.pageNum:'')+'&'+thi.args);


            $.ajax(thi.baseurl,{
                data:args,
                dataType:thi.option.dataType||'json',
                success:thi.finishFn,
                complete:function(data){
                    thi.type = 0;
                    thi.option.complete&&thi.option.complete(data);
                }
            });

            return true;
        },
        next:function(){
            this.nowPage++;
            if(!this.getData())
                this.nowPage--;
        }
    };

    /**
     * 预处理ajax回来的数据,让调用这个方法,只要处理正确数据就行
     * @param data string
     * @param fn function
     * @param [errFns] obj 处理指定错误的方法,{3:function(){}}可以处理返回码为3的报错,如果key为all,则表示所有没有在 errFns中出现错误处理方法的,都使用这个方法
     * */
    H.preData = function (data,fn,errFns){
        data = H.json(data,'json');
        if(errFns&&errFns['no']&&data.no){
            errFns['no'](data.no)
        }
        if(data.status==1){
            fn&&fn(data.data);
        }else if(errFns&&(errFns[data.status]||errFns['all'])){
            if(errFns[data.status])
                errFns[data.status](data);
            else
                errFns['all'](data);
        }else
            alert(data.info||data);
    };

    //与 $.get 一样的,只是封装了对返回的数据不是json格式或者 status不等于1的情况下的处理
    H.get = function (url,data,fn,errFns){
        get_postBase('get',url,data,fn,errFns);
    };
    //与 $.post 一样的,只是封装了对返回的数据不是json格式或者 status不等于1的情况下的处理
    H.post = function (url,data,fn,errFns){
        get_postBase('post',url,data,fn,errFns);
    };
    
    function get_postBase(method,url,data,fn,errFns){
        if(typeof data=='function'){
            errFns = errFns||fn;
            fn = data;
            data = '';
        }
        $[method](url,data,function (data){
            H.preData(data,fn,errFns)
        },'text');
    }

    /**
     * 把字符串转为json或者json转为字符串
     * @param data allkind 支持所有数据格式，但是如果是非json格式的字符串，或者转换失败会返回本身字符串
     * @param [tranTo] String 强制转换为什么格式，如果本身就是那种格式，会返回原数据
     *						(主要用于不确定原数据是什么格式，但是又有可能需要转格式的时候)
     *						值：string ：原始字符串数据
     *							json ：js对象
     */
    H.json = function(data,tranTo){
        if(!data)return '';

        if(typeof data=='string'){//origin -> json
            //data = data.replace(/\s*/g,'');//把所有空格都去掉
            data = H.trim(data);
            var firstLett = data.slice(0,2);
            if(tranTo=='string'||firstLett.search(/\[|\{|\"|\'/i)<0){//如果这个原本是字符串的，直接返回就行了，url编码会自动解码的
                return data
            }
            try{
                var i=0;
                while(typeof data=='string'&&i<5){//不知道为啥，存进localStage里的数据取出来的时候要JSON.parse两次...
                    data = JSON.parse(data);
                    ++i;
                }
            }
            catch(e){console.error('[Hquery error]'+e)}

            return data
        }else{//json -> origin
            if(tranTo=='json'){
                return data
            }
            return JSON.stringify(data)
        }
    };

    /**
     * 除了清除字符串前后的空格，还能清除DOM对象的空格元素
     * */
    H.trim = function(str){
        if(typeof str=='string')
            return str.replace(/(^\s+)|(\s+$)/g,'');
        else if(str instanceof HTMLElement&&str.childNodes){
            var childrens = str.childNodes,i=0;
            for(;i<childrens.length;){
                if(childrens[i].nodeType==3){
                    str.removeChild(childrens[i]);
                }else
                    i++;
            }
        }else{
            return str;
        }
    };

    /**
     * 找到需要加载原图的元素，并加载
     * 要求这些元素有 needRealImg 这个类，而且 imgSrc 属性写好图片的src
     * 现在的图片会显示默认图，图片加载完成之后才会改成原图
     * ps1. 加载中的图片会有 loadingRealImg class，加载完或者失败的，会删掉这个class和 needRealImg class，
     * 加载失败会添加 loadRealImgErr class，方便css选择器来用
     * ps2.加载中的图片推荐显示这张图 /mobile/themes/images/icons/def.jpg
     * */
    H.showRealImg = function(){
        $('body .needRealImg:not(.loadingRealImg)').each(function(){
            var $this = $(this),
                realImg = $this.attr('imgSrc');
            if(realImg){
                var image = new Image();
                image.onload = function(){
                    setUrlToDom($this,realImg);
                    $this.removeClass('needRealImg loadingRealImg');
                };
                image.onerror = image.onabort = function(){
                    err($this);
                };
                image.src = realImg;
                $this.addClass('loadingRealImg');
            }else{
                err($this);
            }
        });

        function setUrlToDom($this,url){
            if($this[0].nodeName=='IMG')
                $this.attr('src',url);
            else
                $this.css('background-image','url('+url+')');
        }

        function err($this){
            setUrlToDom($this,'themes/images/icons/fail.jpg');
            $this.removeClass('needRealImg loadingRealImg');
            $this.addClass('loadRealImgErr');
        }
    };
}());